# -*- coding: utf-8 -*-

"""
Это тупая реализация Reactor-а. Сделано, что бы было.

В частности, каждое соединение обслуживается отдельной микронитью (Loop-ом).
"""

import sys
import time

import eof.log  as log
import eof.util as U
import eof.ztk  as ztk

import diesel

from . import address
from . import ifaces


#{ xxx

def getDefault():
	return sys.modules[__name__]

def getCurrent():
	return sys.modules[__name__]


#{ diesel.app

def install(app):
	assert ztk.queryInterface(ifaces.IReactor.__identifier__)
	global application
	application = app

application = None


#{ ifaces.IReactorCore

def __getattr__(key):
	if key == 'running':
		return application is not None and application.hub.run
	raise AttributeError(key)

def resolve(name, timeout):
	raise NotImplementedError

def run():
	raise NotImplementedError

def stop():
	application.halt()

def crash():
	raise NotImplementedError

def iterate(delay):
	raise NotImplementedError

def fireSystemEvent(eventType):
	raise NotImplementedError

def addSystemEventTrigger(phase, eventType, func, *args, **kwargst):
	raise NotImplementedError

def removeSystemEventTrigger(triggerID):
	raise NotImplementedError

def callWhenRunning(func, *args, **kwargs):
	raise NotImplementedError


#{ ifaces.IReactorTime

def seconds():
	return time.time()

def callLater(delay, func, *args, **kwargs):
	timer = application.hub.call_later(delay, func, *args, **kwargs)
	# @todo: return IDelayedCall(timer)

def getDelayedCalls():
	raise NotImplementedError


#{ ifaces.IReactorTCP

def listenTCP(port, factory, backlog=50, interface=''):
	def handler(address):
		# @note: после выхода их этого метода, `Loop` будет завершён, а вместе с ним и `Connection`

		loop = diesel.core.current_loop
		transport = None

		queue = diesel.Queue()

		transport = DieselTransport(loop, queue)
		protocol = factory.buildProtocol(address)
		protocol.makeConnection(transport)

		try:
			while True:
				try:
					event, value = loop.first(receive_any=True, waits=[queue])
				except diesel.ConnectionClosed:
					break

				if event == 'receive_any':
					protocol.dataReceived(value)
				elif event == queue:
					if value is None:  # close kick
						pass
					elif type(value) is bytes:
						loop.send(value)
					else:
						for item in value:
							loop.send(item)
				else:
					raise RuntimeError('Unknown event', event, value)

				if transport.disconnecting and queue.is_empty:
					break
		finally:
			if transport is not None and not transport.disconnecting:
				U.safecall(transport.loseConnection)
				# @note: при завершении этого Loop-а все соединения порвутся, можно не делать этого явно

	service = diesel.TCPServer(handler, port, interface)
	sm = ztk.getSiteManager()

	@ztk.adapter(diesel.IServiceStartedEvent)
	def on_started(event):
		# @xxx: ...
		if event.object is service:
			factory.doStart()
			log.info('{factory} started on {address}', factory=factory, address=service.sock.getsockname())

	@ztk.adapter(diesel.IServiceStoppingEvent)
	def on_stopping(event):
		# @xxx: ...
		if event.object is service:
			U.safecall(factory.doStop)
			sm.unregisterHandler(on_started)
			sm.unregisterHandler(on_stopping)

	sm.registerHandler(on_started)
	sm.registerHandler(on_stopping)

	try:
		application.add_service(service)
	except Exception:
		on_stopped()
		raise

	return DieselPort(service)


def connectTCP(host, port, factory, timeout=30, bindAddress=None):
	def connect():
		client = diesel.Client(host, port, timeout=timeout, bind_addr=bindAddress)
		assert client.conn is not None, client
		assert client.connected,        client
		transport = DieselTransport(client.conn)
		factory.doStart()
		address  = 1/0
		protocol = factory.buildProtocol(address)
		protocol.makeConnection(transport)

	loop = diesel.fork(connect)
	# @todo: return IConnector


def connectSSL(host, port, factory, contextFactory, timeout=30, bindAddress=None):
	raise NotImplementedError


def connectUNIX(address, factory, timeout=30, checkPID=False):
	raise NotImplementedError


# [eof.ztk]
#class Queue
#class QueueTimeout
#class Loop -> blocking / wake


@ztk.implementer(ifaces.ITransport)
class DieselTransport:
	disconnecting = False

	def __init__(self, loop, queue):
#		self.__client = client
		self.__loop  = loop
		self.__queue = queue

		conn = loop.check_connection(raise_errors=False)

		host, port = conn.addr
#		host, port = conn.sock.getpeername()
		self.__peeraddr = address.IPv4Address(type='TCP', host=host, port=port)
		host, port = conn.sock.getsockname()
		self.__hostaddr = address.IPv4Address(type='TCP', host=host, port=port)

	def write(self, data):
#		conn = getattr(self.__client, 'conn', None)
#		if conn is None or conn.closed:
#			# @todo: (?) Twisted Exception
#			raise diesel.ConnectionClosed('Cannot complete TCP socket operation: connection is closed')
#		conn = self.__conn
#		conn.queue_outgoing(data)
#		conn.set_writable(True)
		if self.disconnecting:
			raise diesel.ConnectionClosed('Cannot complete TCP socket operation: connection is closed')
		self.__queue.put(data)

	def writeSequence(self, data):
#		conn = getattr(self.__client, 'conn', None)
#		if conn is None or conn.closed:
#			# @todo: (?) Twisted Exception
#			raise diesel.ConnectionClosed('Cannot complete TCP socket operation: connection is closed')
#		conn = self.__conn
#		for item in data:
#			conn.queue_outgoing(item)
#		conn.set_writable(True)
		if self.disconnecting:
			raise diesel.ConnectionClosed('Cannot complete TCP socket operation: connection is closed')
		self.__queue.put(data)

	def loseConnection(self):
		# @see: L{listenTCP.handler}
		self.disconnecting = True
		self.__queue.put(None)

	def getPeer(self):
		return self.__peeraddr

	def getHost(self):
		return self.__hostaddr


@ztk.implementer(ifaces.IListeningPort)
class DieselPort:
	def __init__(self, service):
		self.service = service

	def startListening(self):
		raise NotImplementedError

	def stopListening(self):
		# @xxx: [bw] недоступно в diesel
		raise NotImplementedError

	def getHost(self):
		raise NotImplementedError


ztk.moduleProvides(ifaces.IReactorCore, ifaces.IReactorTime, ifaces.IReactorTCP)
